import { Component, cls, type StyledProps } from '@knno/jsx';
import SVGPDF from '@mdi/svg/svg/file-cad.svg';
import SVGText from '@mdi/svg/svg/file-document.svg';
import SVGExcel from '@mdi/svg/svg/file-excel.svg';
import SVGZip from '@mdi/svg/svg/file-export.svg';
import SVGImage from '@mdi/svg/svg/file-image.svg';
import SVGAudio from '@mdi/svg/svg/file-music.svg';
import SVGPowerPoint from '@mdi/svg/svg/file-powerpoint.svg';
import SVGFile from '@mdi/svg/svg/file-question.svg';
import SVGVideo from '@mdi/svg/svg/file-video.svg';
import SVGWord from '@mdi/svg/svg/file-word.svg';
import SVGH5 from '@mdi/svg/svg/language-html5.svg';
import SVGRemove from '@mdi/svg/svg/trash-can-outline.svg';
// import SVGAdd from '@mdi/svg/svg/plus.svg';
import SVGAdd from '../../svg/add.svg';
import { vars } from '../../theme/vars.css';
import { readFileAsDataURL, selectFiles } from '../../tools/upload';
import { Icon } from '../icon/icon';
import { filesStyle } from './files.css';

type ServerItem = {
	type: 'server';
	id: string;
	name: string;
	url: string;
	length?: number;
};

type ClientItem = {
	type: 'client';
	file: File;
	name: string;
	url?: string;
};

type FileItem = ServerItem | ClientItem;

function isClientItem(item: FileItem): item is ClientItem {
	return item.type === 'client';
}

function getFileName(item: FileItem): string {
	if (isClientItem(item)) {
		return item.file.name;
	} else {
		return item.name;
	}
}

async function getFileUrl(item: FileItem): Promise<string> {
	if (isClientItem(item)) {
		if (item.url) return item.url;
		item.url = await readFileAsDataURL(item.file);
		return item.url;
	} else {
		return item.url;
	}
}
const IMG_EXT = /\.(png|jpg|jpeg|gif|svg|webp)$/i;
const IMAGE_EXT = /\.(png|jpg|jpeg|gif|bmp|tiff?|svg|webp)$/i;
const WORD_EXT = /\.(doc|docx|rtf|docm|odt|ods|odf)$/i;
const EXCEL_EXT = /\.(xls|xlsx|xlsm|csv)$/i;
const PPT_EXT = /\.(ppt|pptx|ppat|odp)$/i;
const TXT_EXT = /\.(txt|log|md|markdown)$/i;
const PDF_EXT = /\.pdf$/i;
const ZIP_EXT = /\.(zip|rar|gz|tar|xz)$/i;
const AUDIO_EXT = /\.(mp3|aac|wav|mid|m4a)$/i;
const VIDEO_EXT = /\.(mp4|3gpp|ogg)$/i;
const H5_EXT = /\.(x?html?)$/i;

export function getFileIcon(item: FileItem): Node {
	let name: string;
	if (item.type === 'client') {
		name = (item as ClientItem).file.name;
	} else {
		name = (item as ServerItem).name;
	}
	if (IMAGE_EXT.test(name)) {
		return <Icon svg={SVGImage} color="rgb(61, 173, 61)" size="auto" />;
	} else if (WORD_EXT.test(name)) {
		return <Icon svg={SVGWord} color="rgb(31, 73, 125)" size="auto" />;
	} else if (EXCEL_EXT.test(name)) {
		return <Icon svg={SVGExcel} color="rgb(0, 128, 0)" size="auto" />;
	} else if (PPT_EXT.test(name)) {
		return <Icon svg={SVGPowerPoint} color="rgb(255, 102, 0)" size="auto" />;
	} else if (TXT_EXT.test(name)) {
		return <Icon svg={SVGText} color="rgb(123, 126, 190)" size="auto" />;
	} else if (PDF_EXT.test(name)) {
		return <Icon svg={SVGPDF} color="rgb(204, 0, 0)" size="auto" />;
	} else if (ZIP_EXT.test(name)) {
		return <Icon svg={SVGZip} color="rgb(128, 0, 128)" size="auto" />;
	} else if (AUDIO_EXT.test(name)) {
		return <Icon svg={SVGAudio} color="rgb(220, 174, 0)" size="auto" />;
	} else if (VIDEO_EXT.test(name)) {
		return <Icon svg={SVGVideo} color="rgb(0, 102, 204)" size="auto" />;
	} else if (H5_EXT.test(name)) {
		return <Icon svg={SVGH5} color="rgb(245, 155, 0)" size="auto" />;
	} else {
		return <Icon svg={SVGFile} color={vars.color.secondaryText} size="auto" />;
	}
}

export type Appearance = 'icon' | 'list';

export type FilesProps = StyledProps<Files> & {
	files?: FileItem[];
	appearance?: Appearance;
	readOnly?: boolean;
	max?: number;
	disabled?: boolean;
	reorder?: boolean;
	accept?: string;
	onChange?: (this: Files) => void;
	onClickFileItem?: (this: Files, fileItem: FileItem, index: number) => void;
};

export class Files extends Component<FilesProps> {
	protected render(): Node {
		return <div class={cls(this.props.class, filesStyle)} style={this.props.style}></div>;
	}

	constructor(props: FilesProps) {
		super(props);
		this.setAppearance(this.appearance);
		this.setReadonly(this.readOnly);
		this.setDisabled(this.disabled);
		this.setMax(this.max);
	}

	async selectFiles() {
		const files = await selectFiles({
			mimeType: this.props.accept,
			multiple: true,
		});
		if (files.length > 0) {
			this.addFiles(files.map((f) => ({ type: 'client', file: f, name: f.name })));
			this.el.querySelector<HTMLDivElement>(':scope>div:last-child')?.focus();
			this.props.onChange?.call(this);
		}
	}

	private plusButton(): Node | '' {
		if (!this.readOnly && !this.disabled && this.files.length < (this.max ?? Number.MAX_VALUE)) {
			return (
				<div
					class="add"
					innerHTML={SVGAdd}
					tabIndex={this.disabled || this.readOnly ? undefined : 0}
					onClick={() => this.selectFiles()}
					onKeyPress={(evt) => {
						if (evt.key === 'Enter') {
							evt.stopPropagation();
							this.selectFiles();
						}
					}}
				/>
			);
		} else {
			return '';
		}
	}

	private renderContent() {
		let dragIdx: number | null = null;
		this.el.replaceChildren(
			...this.files.map((item, idx) => {
				let icon: HTMLElement;
				const name = getFileName(item);
				if (IMG_EXT.test(name)) {
					icon = (<img draggable={false} alt={name} />) as HTMLElement;
					getFileUrl(item).then((url) => {
						icon.setAttribute('src', url);
					});
				} else {
					icon = (<div class="icon">{getFileIcon(item)}</div>) as HTMLElement;
				}
				const div = (
					<div
						title={name}
						tabIndex={this.disabled ? undefined : 0}
						onClick={() => {
							if (this.disabled) return;
							this.props.onClickFileItem?.call(this, item, idx);
						}}
						onKeyPress={(evt) => {
							if (evt.key === 'Enter') {
								if (this.disabled) return;
								evt.stopPropagation();
								this.props.onClickFileItem?.call(this, item, idx);
							}
						}}
						onKeyDown={(evt) => {
							if (evt.key === 'Delete' || evt.key === 'Backspace') {
								if (this.disabled || this.readOnly) return;
								evt.stopPropagation();
								this.setFiles(this.files.filter((_, i) => i !== idx));
								if (idx < this.files.length) {
									this.el
										.querySelector<HTMLDivElement>(':scope>div:nth-child(' + (idx + 1) + ')')
										?.focus();
								} else {
									if (this.files.length > 0) {
										this.el
											.querySelector<HTMLDivElement>(
												':scope>div:nth-child(' + this.files.length + ')',
											)
											?.focus();
									} else {
										this.el.querySelector<HTMLDivElement>(':scope>div:last-child')?.focus();
									}
								}
							}
						}}
					>
						{icon}
						<div class="name">{name}</div>
					</div>
				) as HTMLDivElement;
				if (!this.readOnly && !this.disabled) {
					if (this.reorder) {
						div.setAttribute('draggable', 'true');
					}
					div.append(
						// biome-ignore lint/a11y/useKeyWithClickEvents: <>
						<div
							class="del"
							innerHTML={SVGRemove}
							onClick={(evt) => {
								if (this.disabled || this.readOnly) return;
								evt.stopPropagation();
								this.setFiles(this.files.filter((_, i) => i !== idx));
							}}
						/>,
					);
					div.addEventListener('dragover', (ev) => {
						ev.preventDefault();
					});
					div.addEventListener('dragstart', () => {
						div.classList.add('draging');
						dragIdx = idx;
					});
					div.addEventListener('dragend', () => {
						div.classList.remove('draging');
					});
					div.addEventListener('dragenter', () => {
						div.classList.add('dropping');
					});
					div.addEventListener('dragleave', () => {
						div.classList.remove('dropping');
					});
					div.addEventListener('drop', (ev) => {
						div.classList.remove('dropping');
						if (idx === dragIdx) return;
						const target = this.files[idx];
						if (dragIdx != null) {
							const removed = this.files.splice(dragIdx, 1);
							let targetIndx = this.files.indexOf(target);
							if (dragIdx < idx) {
								targetIndx++;
							}
							this.files.splice(targetIndx, 0, ...removed);
							this.renderContent();
						}
						ev.preventDefault();
					});
				}
				return div;
			}),
			this.plusButton(),
		);
	}

	get readOnly(): boolean {
		return this.props.readOnly ?? false;
	}
	setReadonly(value: boolean): this {
		this.props.readOnly = value;
		this.renderContent();
		return this;
	}

	get reorder(): boolean {
		return this.props.reorder ?? false;
	}
	setReorder(value: boolean): this {
		this.props.reorder = value;
		this.renderContent();
		return this;
	}

	get disabled(): boolean {
		return this.props.disabled ?? false;
	}
	setDisabled(value: boolean): this {
		this.props.disabled = value;
		this.el.classList.toggle('disabled', value);
		this.renderContent();
		return this;
	}

	get appearance(): Appearance {
		return this.props.appearance ?? 'icon';
	}
	setAppearance(appearance: Appearance): this {
		this.props.appearance = appearance;
		this.el.classList.remove('icon', 'list');
		this.el.classList.add(appearance);
		return this;
	}

	get max(): number | null {
		return this.props.max ?? null;
	}
	setMax(value: number | null): this {
		this.props.max = value === null ? undefined : Math.max(0, value);
		if (this.props.max && this.props.files && this.props.files.length > this.props.max) {
			this.props.files = this.props.files.slice(0, this.props.max);
		}
		this.renderContent();
		return this;
	}

	get files(): FileItem[] {
		return this.props.files ?? [];
	}
	setFiles(value: FileItem[]): this {
		this.props.files = value;
		this.setMax(this.max);
		return this;
	}

	addFiles(files: FileItem[]): this {
		this.setFiles([...this.files, ...files]);
		return this;
	}

	onChange(handler: ((this: Files) => void) | undefined): this {
		this.props.onChange = handler;
		return this;
	}

	onClickFileItem(
		handler: ((this: Files, fileItem: FileItem, index: number) => void) | undefined,
	): this {
		this.props.onClickFileItem = handler;
		return this;
	}
}
